/** @file    JabberGlooxImpl.cpp
 *  @author  Alessandro Polo
 *  @version $Id: JabberGlooxImpl.cpp 2839 2010-08-04 21:48:26Z alex $
 *  @brief
 * File containing methods for the JabberGlooxImpl class.
 * The header for this class can be found in JabberGlooxImpl.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "JabberGlooxImpl.h"

 #include <framework/network/NetworkUtilities.h>
 
 #include <client.h>
 #include <disco.h>
 #include <logsink.h>
 #include <connectionhttpproxy.h>
 #include <connectiontcpclient.h>
 #include <rostermanager.h>


using namespace wosh;
// gloox bug: its not included?
namespace gloox {
 const std::string EmptyString = "";
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

JabberGlooxImpl::JabberGlooxImpl( wosh::BundleGeneric& bundle )
	: wosh::BundleGenericWorker(bundle) {
	this->jabbListener = NULL;

	this->xmppJclient = NULL;
	this->xmppConnClient = NULL;
	this->xmppConnProxy = NULL;
	this->xmppJRM = NULL;

	this->xmppServerAddress = "";
	this->xmppServerPort = 0;
	this->xmppServerProxy = "";

	this->autoReconnectMode = 0;
	this->autoReconnectDelay = 20;
	this->autoReconnectRetries = 10;

	this->failureType = (int)FAILURE_NONE;
	this->failureDescription = "";

	this->xmppAccountName = "";
	this->xmppAccountPwd = "";

	this->xmppAvailable = true;
	this->xmppPresenceStatus = "wosh";
	this->xmppPriority = 5;
 }


JabberGlooxImpl::~JabberGlooxImpl() {
	Log(LOG_DEBUG, ":~JabberGlooxImpl(): Destroying.." );
	if ( isThreadRunning() ) {
		Log(LOG_WARNING, ":~JabberGlooxImpl(): Still running! Forcing quitThread.." );
		quitThread();
	 }
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool JabberGlooxImpl::connect() {
	Log(LOG_VERBOSE, ":connect(): Connecting to XMPP Server [%s @ %s port %d]" , this->xmppAccountName.c_str(), this->xmppServerAddress.c_str(), this->xmppServerPort );
	bool connected = this->xmppJclient->connect( false ); // SYNCH
	if ( !connected ) {
		Log(LOG_CRITICAL, ":connect(): Can't connect to XMPP Server! Exiting (FAILURE)" );
		return false;
	 }
	Log(LOG_INFO, ":connect(): Connected to XMPP Server [%s port %d]" , this->xmppServerAddress.c_str(), this->xmppServerPort );
	return true;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////// RUN THREAD

bool JabberGlooxImpl::initializingThread() {
	Log(LOG_DEBUG, ":initializingThread(): STARTING" );
	// setup account, this fields must be set BEFORE thread is started and cannot be changed while running
	gloox::JID jid( this->xmppAccountName );
	this->xmppJclient = new gloox::Client( jid, this->xmppAccountPwd, this->xmppServerPort );
	this->xmppJclient->setServer( this->xmppServerAddress );
	this->xmppJclient->setPort( this->xmppServerPort ); //clientbase
	// map log for debugging
	this->xmppJclient->logInstance().registerLogHandler( gloox::LogLevelDebug, gloox::LogAreaAll, this ); //clientbase
	// setup common (optional) config
	this->xmppJclient->disco()->setVersion( "wosh::devices::JabberGlooxBundle", "1.20" ); //clientbase
	this->xmppJclient->disco()->setIdentity( "client", "wosh::devices::JabberGlooxBundle" ); //clientbase
	setPresence(this->xmppAvailable, true);
	// register callbacks: connection interrupts; incoming messages; presence detector, subscription requests
	this->xmppJclient->registerConnectionListener( this );
	this->xmppJclient->registerMessageHandler( this );
	this->xmppJclient->registerPresenceHandler( this );
	this->xmppJclient->registerSubscriptionHandler( this );
	if ( !this->xmppServerProxy.empty() ) {
		std::string proxyAddress = wosh::network::NetworkUtilities::getAddressFromDestination(this->xmppServerProxy);
		int proxyPort = wosh::network::NetworkUtilities::getPortFromDestination(this->xmppServerProxy);
		Log(LOG_INFO, ":initializingThread(): Setting up Proxy: %s port %d..", proxyAddress.c_str(), proxyPort );
		this->xmppConnClient = new gloox::ConnectionTCPClient(this->xmppJclient->logInstance(), proxyAddress, proxyPort);
		this->xmppConnProxy = new gloox::ConnectionHTTPProxy(this->xmppJclient, this->xmppConnClient, this->xmppJclient->logInstance(), this->xmppServerAddress, this->xmppServerPort );
		this->xmppJclient->setConnectionImpl( this->xmppConnProxy );
	 }
	// alloc RosterManager, for subscription management
	this->xmppJRM = new gloox::RosterManager(this->xmppJclient);
	// thread will exit on failure:
	return connect();
 }

void JabberGlooxImpl::runThread() {
	Log(LOG_DEBUG, ":runThread(): Entering recv Loop.." );
	gloox::ConnectionError ce = gloox::ConnNoError;
	bool receive = true; long tried = 0;
	while(true) {
		ce = gloox::ConnNoError;
		this->failureType = (int)FAILURE_NONE; this->failureDescription = "";

		if ( receive ) ++tried;
	//	setPresence(this->xmppAvailable, true);
		// receive data: synch, 2 seconds timeout
		while( receive && ce == gloox::ConnNoError ) {
			setThreadAlive();
			ce = this->xmppJclient->recv( 2 );
			if ( !this->running ) break; // =break this reciv loop, but will also exit
		 }
		// eval state and reconnection settings
		if ( !this->running ) break; // =exit
		if ( ce != gloox::ConnNoError ) {
			Log(LOG_DEBUG, ":runThread(): Loop broken due to connection error #%d [%s]", (int)ce, getConnectionErrorAsString(ce) );
			this->failureType = (int)FAILURE_CONNECTION;
			this->failureDescription = getConnectionErrorAsString(ce);
			// RAISE A SYSTEM-ERROR
		 }
		if ( this->autoReconnectMode == 0 ) break;
		if ( this->autoReconnectMode == 1 ) {
			bool goOn = true;
			switch(ce) { // reconnection doesn't apply to some errors
				case gloox::ConnAuthenticationFailed:
				case gloox::ConnUserDisconnected:
				case gloox::ConnDnsError:
				case gloox::ConnTlsNotAvailable:
				case gloox::ConnNoSupportedAuth: {
						goOn = false; break; //=> =exit
					 }
				default:
						goOn = true;
			 }
			if ( !goOn ) {
				Log(LOG_VERBOSE, ":runThread(): Exiting (reconnect aborted) due to connection error #%d [%s]", (int)ce, getConnectionErrorAsString(ce) );
				break; // =exit
			 }
		 }
		if ( this->autoReconnectRetries != 0 && this->autoReconnectRetries <= tried ) {
			Log(LOG_INFO, ":runThread(): Exiting thread due to retries limit (%ld)", this->autoReconnectRetries );
			this->failureType |= (int)FAILURE_RETRY_LIMIT;
			break; // =exit
		 }
		if ( !Thread::sleepWhile(this, this->autoReconnectDelay*1000, 1000, &this->running) ) {
			Log(LOG_DEBUG, ":runThread(): Stopped while waining for reconnection.." );
			break; // =exit
		 }
		Log(LOG_INFO, ":runThread(): Reconnecting.. [%ld]", tried );
		receive = connect();
		Log(LOG_DEBUG, ":runThread(): Reconnected?=%d", receive );
	 }
	if ( !receive )
		Log(LOG_WARNING, ":runThread(): once exited, 'receive' is FALSE" );

	Log(LOG_DEBUG, ":runThread(): Exiting" );
 }

void JabberGlooxImpl::exitingThread() {
	Log(LOG_INFO, ":exitingThread() : Disconnecting.." );
	this->xmppJclient->disconnect();
	Log(LOG_VERBOSE, ":exitingThread() : Destroying Jabber Client.." );
	delete this->xmppJclient; this->xmppJclient = NULL; this->xmppJRM = NULL;
	if ( this->xmppConnClient != NULL ) {
		Log(LOG_DEBUG, ":exitingThread() : Destroying ConnectionTCPClient.." );
		delete this->xmppConnClient; this->xmppConnClient = NULL;
	 }
	if ( this->xmppConnProxy != NULL ) {
		Log(LOG_DEBUG, ":exitingThread() : Destroying ConnectionHTTPProxy.." );
		delete this->xmppConnProxy; this->xmppConnProxy = NULL;
	 }
 }

////////////////////////////////////////////////////////////////////////////////////////////////// RUN THREAD
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////// LogHandler INTERFACE

void JabberGlooxImpl::handleLog( gloox::LogLevel level, gloox::LogArea area, const std::string& message ) {
	if ( !Log.isDebug() ) return;
	Log(LOG_DEBUG, ":handleLog(): %d, area: %d, %s", level, area, message.c_str() );
 }

//////////////////////////////////////////////////////////////////////////////////////// LogHandler INTERFACE
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////// ConnectionListener INTERFACE

bool JabberGlooxImpl::onTLSConnect( const gloox::CertInfo& cert ) {
	Log(LOG_INFO, ":onTLSConnect(): Secure Connected to %s by %s!", cert.server.c_str(), cert.issuer.c_str() );
	// validate server
	//this->jabbListener->getCredentialsIO
// Secure Connection to C=US,ST=California,L=Mountain View,O=Google Inc.,CN=gmail.com by C=US,O=Equifax,OU=Equifax Secure Certificate Authority
	// callback to bundle to setup credentials
	return true;
 }

void JabberGlooxImpl::onConnect() {
	Log(LOG_INFO, ":onConnect(): Connection estabilished!" );
 }

void JabberGlooxImpl::onDisconnect( gloox::ConnectionError error ) {
	if ( error == gloox::ConnUserDisconnected ) {
		Log(LOG_INFO, ":onDisconnect(): Disconnected (on request)" );
		return;
	 }
	if ( error != gloox::ConnNoError )
		Log(LOG_WARNING, ":onDisconnect(): Disconnected ERROR#%d [%s]!", (int)error, getConnectionErrorAsString(error) );
	if ( this->running == false )
		Log(LOG_DEBUG, ":onDisconnect(): 'running' is false, looks like stop was requested" );
 }

//////////////////////////////////////////////////////////////////////////////// ConnectionListener INTERFACE
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT JabberGlooxImpl::subscribe( const std::string& account_name, const std::string& opt_message ) {
	if ( account_name.size() < 3 ) return WRET_ERR_PARAM;
	if ( !isThreadRunning() ) {
		Log(LOG_CRITICAL, ":subscribe(%s): Thread not running!", account_name.c_str() );
		return WRET_ERR_ILLEGAL_USE;
	 }
	if ( this->xmppJRM == NULL ) {
		Log(LOG_CRITICAL, ":subscribe(%s): NULL RosterManager!", account_name.c_str() );
		return WRET_ERR_ILLEGAL_USE;
	 }
	this->xmppJRM->subscribe( gloox::JID(account_name), "", gloox::StringList(), opt_message );
	//this->xmppJRM->synchronize();
	return WRET_OK;
 }

WRESULT JabberGlooxImpl::unSubscribe( const std::string& account_name, const std::string& opt_message ) {
	if ( account_name.size() < 3 ) return WRET_ERR_PARAM;
	if ( !isThreadRunning() ) {
		Log(LOG_CRITICAL, ":unSubscribe(%s): Thread not running!", account_name.c_str() );
		return WRET_ERR_ILLEGAL_USE;
	 }
	if ( this->xmppJRM == NULL ) {
		Log(LOG_CRITICAL, ":unSubscribe(%s): NULL RosterManager!", account_name.c_str() );
		return WRET_ERR_ILLEGAL_USE;
	 }
	this->xmppJRM->unsubscribe( gloox::JID(account_name), opt_message );
	//this->xmppJRM->synchronize();
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void JabberGlooxImpl::setAutoReconnectMode( int value )		{ this->autoReconnectMode = value; }
void JabberGlooxImpl::setAutoReconnectDelay( long value )	{ this->autoReconnectDelay = value; }
void JabberGlooxImpl::setAutoReconnectRetries( long value )	{ this->autoReconnectRetries = value; }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool JabberGlooxImpl::setServer( const std::string& address, int port, const std::string& proxy ) {
	if ( isConnected() ) {
		Log(LOG_CRITICAL, ":setServer(%s, %d, %s) : already Connected!", address.c_str(), port, proxy.c_str() );
		return false;
	 }
	this->xmppServerAddress = address;
	this->xmppServerPort = port;
	this->xmppServerProxy = proxy;
	Log(LOG_DEBUG, ":setServer(%s, %d, %s)", address.c_str(), port, proxy.c_str() );
	return true;
 }

bool JabberGlooxImpl::setServerAddress( const std::string& address ) {
	if ( isConnected() ) {
		Log(LOG_CRITICAL, ":setServerAddress(%s) : already Connected!", address.c_str() );
		return false; }
	this->xmppServerAddress = address;
	Log(LOG_DEBUG, ":setServerAddress(%s)", address.c_str() );
	return true;
 }

bool JabberGlooxImpl::setServerPort( int port ) {
	if ( isConnected() ) {
		Log(LOG_CRITICAL, ":setServerPort(%d) : already Connected!", port );
		return false; }
	this->xmppServerPort = port;
	Log(LOG_DEBUG, ":setServerPort(%d)", port );
	return true;
 }

bool JabberGlooxImpl::setServerProxy( const std::string& address ) {
	if ( isConnected() ) {
		Log(LOG_CRITICAL, ":setServerProxy(%s) : already Connected!", address.c_str() );
		return false; }
	this->xmppServerProxy = address;
	Log(LOG_DEBUG, ":setServerProxy(%s)", address.c_str() );
	return true;
 }

bool JabberGlooxImpl::setAccount( const std::string& name, const std::string& pwd ) {
	if ( isConnected() ) {
		Log(LOG_CRITICAL, ":setAccount(%s, *) : already Connected!", name.c_str() );
		return false; }
	this->xmppAccountName = name;
	this->xmppAccountPwd = pwd;
	Log(LOG_DEBUG, ":setAccount(%s, *[%d])", name.c_str(), pwd.size() );
	return true;
 }

bool JabberGlooxImpl::setAccountUserName( const std::string& name ) {
	if ( isConnected() ) {
		Log(LOG_CRITICAL, ":setAccountUserName(%s) : already Connected!", name.c_str() );
		return false; }
	this->xmppAccountName = name;
	Log(LOG_DEBUG, ":setAccountUserName(%s)", name.c_str() );
	return true;
 }

bool JabberGlooxImpl::setAccountPassword( const std::string& pwd ) {
	if ( isConnected() ) {
		Log(LOG_CRITICAL, ":setAccountPassword(*) : already Connected!" );
		return false; }
	this->xmppAccountPwd = pwd;
	Log(LOG_DEBUG, ":setAccountPassword(*[%d])", pwd.size() );
	return true;
 }

bool JabberGlooxImpl::setPriority( int value ) {
	this->xmppPriority = value;
	return setPresence(this->xmppAvailable);
 }

bool JabberGlooxImpl::setStatusMessage( const std::string& message ) {
	Log(LOG_DEBUG, ":setStatusMessage(%s)", message.c_str() );
	this->xmppPresenceStatus = message;
	return setPresence(this->xmppAvailable);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

const char* JabberGlooxImpl::getConnectionErrorAsString( gloox::ConnectionError ce ) {
	switch(ce) {
		case gloox::ConnAuthenticationFailed:
			return "Authentication failed. Username/password wrong or account does not exist. Maybe account was blocked.";
		case gloox::ConnUserDisconnected:
			return "The user (or higher-level protocol) requested a disconnect.";
		case gloox::ConnNotConnected:
			return "There is no active connection.";
		case gloox::ConnCompressionFailed:
			return "Negotiating/initializing compression failed.";
		case gloox::ConnTlsFailed:
			return "The server's certificate could not be verified or the TLS handshake did not complete successfully.";
		case gloox::ConnTlsNotAvailable:
			return "The server didn't offer TLS while it was set to be required or TLS was not compiled in.";
		case gloox::ConnNoSupportedAuth:
			return "The auth mechanisms the server offers are not supported or the server offered no auth mechanisms at all.";
		case gloox::ConnOutOfMemory:
			return "Out of memory.";
		case gloox::ConnDnsError:
			return "Resolving the server's hostname failed.";
		case gloox::ConnConnectionRefused:
			return "The connection was refused by the server (on the socket level).";
		case gloox::ConnParseError:
			return "An XML parse error occurred.";
		case gloox::ConnIoError:
			return "An I/O error occured.";
		case gloox::ConnProxyNoSupportedAuth:
			return "The HTTP/SOCKS5 proxy requires an unsupported auth mechanism.";
		case gloox::ConnProxyAuthFailed:
			return "HTTP/SOCKS5 proxy authentication failed.";
		case gloox::ConnProxyAuthRequired:
			return "The HTTP/SOCKS5 proxy requires authentication.";
		case gloox::ConnStreamClosed:
			return "The stream has been closed (by the server).";
		case gloox::ConnStreamVersionError:
			return "The incoming stream's version is not supported";
		case gloox::ConnStreamError:
			return "A stream error occured. The stream has been closed.";
		case gloox::ConnNoError:
			return "Disconnected from server without errors";
		default:
			return "unknown error";
	 };
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
